﻿package com.dg.gaming.api.common {

    import com.dg.gaming.api.common.msg.ITransmittable;
    import com.dg.gaming.api.common.msg.Msg;
    import com.dg.gaming.api.common.playerstate.PlayerState;
    import com.dg.gaming.api.common.playerstate.PlayerStateViewing;
    import com.ericfeminella.collections.HashMap;

    import mx.collections.ArrayCollection;
    import mx.collections.Sort;
    import mx.collections.SortField;

    import nanoxml.XMLElement;

    public class PlayerList extends Observable implements ITransmittable {

        /** Number of header tokens. */
        protected static const NUM_OF_HEADER_TOKENS:int = 3;

        /** Number of tokens which a PlayerList flattens to. */
        protected static const NUM_OF_TOKENS:int = 3;

        // Declare constants
        public static const NO_PLAYER:Number = -1;

        private static const XML_ATT_OWNER:String = "owner";

        private static const XML_ATT_CUR_PLAYER:String = "curPlayer";

        // List of players and owner (first player added to a table)
        private var players:HashMap = new HashMap;

        private var owner:Number;

        private var currentPlayerId:Number;

        /**
         * Default constructor.
         */

        public function init(owner:Number):void {
            this.owner = owner; // Setowner
            this.currentPlayerId = NO_PLAYER; // ... no current player
            this.players = new HashMap; // Declare players hash
        }

        /**
         * Constructor which creates a Player object from the flatten () method of
         * another Player object.
         *
         * @param  message    XML element version of object.
         */
        public function initXml(message:XMLElement):void {
            this.init(message.getLongAttribute(XML_ATT_OWNER));
            this.currentPlayerId = message.getLongAttribute(XML_ATT_CUR_PLAYER);

            // Parse the players
            var e:Array = message.chileds;
            var n:int = e.length;
            for (var i:int = 0; i < n; i++) {
                var childElement:XMLElement = XMLElement(e[i]);
                var player:Player = new Player;
                player.initXML(childElement as XMLElement);
                this.players.put(player.playerId, player);
            }
        }

        /**
         * Add a player to the table.
         *
         * @param playerName  Name of the player to add to table.
         * @return true if the playerName was added succesfully.
         */
        public function addPlayer(theUser:User):Boolean {
            var playerId:Number = theUser.userid;

            // If this player is already at the table, then don't add
            if (this.players.containsKey(playerId)) {
                return false;
            }

            // Add the player
            var player:Player = new Player;
            player.init(theUser, new PlayerStateViewing);
            this.players.put(playerId, player);

            // notify any listeners on data
            setChanged();
            notifyObservers("+P " + playerId);

            return true;
        }

        /**
         * Remove a player.
         *
         * @param playerName   Name of player to remove.
         * @return true if the playerName was removed succesfully.
         */
        public function removePlayer(playerId:Number):Boolean {
            // remove player
            if (this.players.remove(playerId) == null) {
                return false;
            }

            // Notify listeners
            setChanged();
            notifyObservers("-P " + playerId);
            return true;
        }

        /**
         * Return the number of users playing at a particular table.
         *
         * @return  Number of players.
         */
        public function size():int {
            return this.players.size();
        }

        /**
         * Return true if the list contains the player.
         *
         * @param  playerName  Username of player
         * @return             True if player is on this list.
         */
        public function containsUser(playerName:Number):Boolean {
            return this.players.containsKey(playerName);
        }

        /**
         * Update the list of players to include the pointers to their users.
         * (This is needed because the messages from the server to the clients
         *  only uses names for players at a table, and therefore when a client
         *  joins a table, he only gets the names of the players at that table
         *  and so we need to go though the list and match them up with the
         *  users that they correspond to.)
         *
         * @param userList   The list of users
         */
        public function updateUsers(userList:UserList):void {
            var allPlayers:Array = getPlayers();

            // Loop through all the players and set the user field to the correct user.
            for (var i:int = 0; i < players.size(); i++) {
                var player:Player = Player(allPlayers[i]);
                if (player.username == null) {
                    player.theUser = userList.getUser(player.playerId);
                }
            }
        }

        /**
         * Return the owner of this table.
         *
         * @return   Username of owner of table.
         */
        public function getOwner():Number {
            return this.owner;
        }

        /**
         * Return a player.
         *
         * @param playerName  Username of player.
         * @return            Player object.
         */
        public function getPlayer(playerId:Number):Player {
            return this.players.getValue(playerId) as Player;
        }

        /**
         * Return a player from a seat number.  Returns null if a player isn't
         * found.
         *
         * @param seatNum   Seat number of a user.
         * @return          Player object.
         */
        public function getPlayerBySeat(seatNum:int):Player {
            var allPlayers:Array = getPlayers();

            // Loop through all the players until you get the correct player
            for (var i:int = 0; i < players.size(); i++) {
                var player:Player = Player(allPlayers[i]);
                if (player.seatNum == seatNum) {
                    return player;
                }
            }
            return null;
        }

        /**
         * Return a vector of players which arn't sorted.
         *
         * @return     Unsorted list of players.
         */
        public function getPlayers():Array {
            // return data
            return this.players.getValues();
        }

        /**
         * Sets the next player
         *
         * @param playerName Next player
         */
        public function nextPlayerById(playerId:Number):void {
            this.currentPlayerId = playerId;
            refreshObservers();
        }

        /**
         * Sets the next player
         *
         * @param player Next player
         */
        public function nextPlayerByPlayer(player:Player):void {
            nextPlayerById(player.playerId);
        }

        /**
         * Sets the next player
         *
         * @param seatNum  The seat number of the next player
         */
        public function nextPlayerSeatNum(seatNum:int):void {
            nextPlayerById(getPlayerBySeat(seatNum).playerId);
        }

        /**
         * Makes the current player equal to the next player.
         */
        public function nextPlayer():void {
            // Find out what seat number the current player is
            var curPlayer:Player = getPlayer(this.currentPlayerId);

            // Retrieve all the players who are actually in a game
            var allPlayers:Array = getPlayersSortedBySeat(PlayerState.GAME_STARTED);

            // If this user is the only user in the list then return this user
            var size:int = allPlayers.size();
            if (allPlayers.size() != 1) {

                // Find location in vector of user
                var index:int = allPlayers.indexOf(curPlayer);

                // If last player then go to start again otherwise increment
                if (index == size - 1) {
                    index = 0;
                } else {
                    index++;
                }

                // index should point to the next player
                var nextPlayer:Player = Player(allPlayers.get(index));
                this.currentPlayerId = nextPlayer.playerId; // update this object
            }
        }

        /**
         * Set current player.
         *
         * @param username   Username of current player.
         */
        public function setCurrentPlayer(userId:Number):void {

            // Set current player
            this.currentPlayerId = userId; // update this object

            // Notify listeners
            setChanged();
            notifyObservers("CP " + userId);
        }

        /**
         * Sets the current player to the person with the smallest seat number.
         */
        public function resetCurrentPlayer():void {
            var players:Array = getPlayersSortedBySeat(PlayerState.GAME_STARTED);

            if (players.length != 0) {
                // retrieve first player and set current player to that
                var firstPlayer:Player = Player(players[0]);
                setCurrentPlayer(firstPlayer.playerId);
            }
        }

        /**
         * Return the currrent layer at a turned based table.
         *
         * @return  Current player in String form.
         */
        public function getCurrentPlayerId():Number {
            return this.currentPlayerId;
        }

        /**
         * Return the currrent layer at a turned based table.
         *
         * @return  Current player as a Player object.
         */
        public function getCurrentPlayer():Player {
            return this.players.getValue(this.currentPlayerId) as Player;
        }

        /**
         * Determine if the given player is the current player or not.
         *
         * @param player   The player to check
         * @return         True if the given player is the current player.
         */
        public function isCurrentPlayer(player:Player):Boolean {
            return player.playerId == this.currentPlayerId;
        }

        private function sortPlayers(players:Array):Array {
            var arrCol:ArrayCollection = new ArrayCollection(players);
            var dataSortField:SortField = new SortField();
            dataSortField.name = "seatNum";
            dataSortField.numeric = true;
            var numericDataSort:Sort = new Sort();
            numericDataSort.fields = [ dataSortField ];


            arrCol.sort = numericDataSort;
            arrCol.refresh();
            return arrCol.source;
        }

        /**
         * Return a vector of all the players in this list which are currently
         * actually playing a game.
         *
         * @param state   State of the player e.g. game in state.
         * @return        List of players sorted by seat.
         */
        public function getPlayersSortedBySeat(state:String = null):Array {

            // 1) Sort - Retrieve the players sorted by seat
            var sortedPlayers:Array = getPlayers();
            sortedPlayers = sortPlayers(sortedPlayers);
            // 2) Filter - Only add the players who are in a particular state
            if (state != null) {

                var statePlayers:Array = new Array;
                var j:int = 0;
                for (var i:int = 0; i < sortedPlayers.length; i++) {
                    var player:Player = Player(sortedPlayers[i]);
                    if (player.getState().stringValue() == state) {
                        statePlayers[j++] = player;
                    }
                }
                sortedPlayers = statePlayers;
            }

            // return data
            return sortedPlayers;
        }

        /**
         * Convience method to return in-game players sorted by seat and
         * in the form of Strings (just the players usernames)
         *
         * @return
         */
        public function getInGamePlayers():Array {
            // Return vector of players sorted by seat
            var v:Array = getPlayersSortedBySeat(PlayerState.GAME_STARTED);
            var inGamePlayers:Array = new Array;
            for (var i:int = 0; i < v.length; i++) {
                inGamePlayers[i] = Player(v[i]).playerId;
            }

            // Return as a String array.
            return inGamePlayers;
        }

        /**
         * Return a vector of all the players in this list which are sorted by
         * seat number (used for the game logic).
         *
         * @return  List of players sorted by seat.
         */

        public function getPlayersSortedBySeatPlayer():Array {

            // sort by seat number
            var v:Array = this.players.getValues();
            // return data
            return v;
        }

        /**
         * Returns the number of players in a particular state.
         *
         * @param state
         * @return  Count of the specified state.
         */
        public function getPlayerStateCount(state:String):int {

            var count:int = 0;

            var vPlayers:Array = getPlayers();
            for (var i:int = 0; i < this.players.size(); i++) {
                var player:Player = Player(vPlayers[i]);
                if (player.getState().stringValue() == state) {
                    count++;
                }
            }

            // return count
            return count;
        }

        /**
         * Return true if a seat at a table is free.
         *
         * @param  seatNum   Seat number to check.
         * @return           If seat is free, return true.
         */
        public function isSeatFree(seatNum:int):Boolean {
            var vPlayers:Array = getPlayers();

            for (var i:int = 0; i < this.players.size(); i++) {
                var player:Player = Player(vPlayers[i]);
                if (player.seatNum == seatNum) {
                    return false; // seat isn't free!
                }
            }

            return true;
        }

        /**
         * Flatten the String so that it can be transmitted.
         *
         * @see com.dg.gaming.api.common.msg.ITransmittable#flatten()
         */
        public function flatten():XMLElement {
            var message:XMLElement = addGeneralAttributes();

            // Add player children
            addPlayers(message, false);

            return message; // turn XML element into a String
        }

        private function addPlayers(message:XMLElement, withUserNam:Boolean):void {
            var vPlayers:Array = getPlayers();
            for (var i:int = 0; i < this.players.size(); i++) {
                var player:Player = Player(vPlayers[i]);
                var chiled:XMLElement = withUserNam ? player.flattenWithUser() : player.flatten();
                message.addChild(chiled);
            }
        }

        private function addGeneralAttributes():XMLElement {
            var message:XMLElement = new XMLElement;
            message.name = Msg.PLAYER_LIST;
            message.setAttribute(XML_ATT_OWNER, this.owner);
            message.setAttribute(XML_ATT_CUR_PLAYER, this.currentPlayerId);
            return message;
        }

        public function flattenWithUser():XMLElement {
            var message:XMLElement = addGeneralAttributes();
            addPlayers(message, true);
            return message; // turn XML element into a String
        }

        /**
         * @see java.lang.Object#toString()
         */
        public override function toString():String {
            var sb:String = "";
            var vPlayers:Array = getPlayers();

            for (var i:int = 0; i < this.players.size(); i++) {
                var player:Player = Player(vPlayers[i]);
                sb += player.toString();
                if (i != this.players.size() - 1) {
                    sb += ", ";
                }
            }

            return sb;
        }

        /**
         * Notify observers.
         *
         * @see java.util.Observable#notifyObservers()
         */
        public function refreshObservers():void {
            setChanged();
            super.notifyObservers();
        }

    }

}